/*!
 Copyright (c) 2014 Roman Dvornov
*/
var OfficeExtension = OfficeExtension || {};
(function (global) {
	var NativePromise = global['Promise'];
	var nativePromiseSupported = NativePromise &&
        'resolve' in NativePromise &&
        'reject' in NativePromise &&
        'all' in NativePromise &&
        'race' in NativePromise &&
        (function () {
        	var resolve;
        	new NativePromise(function (r) { resolve = r; });
        	return typeof resolve === 'function';
        })();
	if (nativePromiseSupported)
		return;
	var PENDING = 'pending';
	var SEALED = 'sealed';
	var FULFILLED = 'fulfilled';
	var REJECTED = 'rejected';
	var NOOP = function () { };
	function isArray(value) {
		return Object.prototype.toString.call(value) === '[object Array]';
	}
	var asyncSetTimer = typeof setImmediate !== 'undefined' ? setImmediate : setTimeout;
	var asyncQueue = [];
	var asyncTimer;
	function asyncFlush() {
		for (var i = 0; i < asyncQueue.length; i++)
			asyncQueue[i][0](asyncQueue[i][1]);
		asyncQueue = [];
		asyncTimer = false;
	}
	function asyncCall(callback, arg) {
		asyncQueue.push([callback, arg]);
		if (!asyncTimer) {
			asyncTimer = true;
			asyncSetTimer(asyncFlush, 0);
		}
	}
	function invokeResolver(resolver, promise) {
		function resolvePromise(value) {
			resolve(promise, value);
		}
		function rejectPromise(reason) {
			reject(promise, reason);
		}
		try {
			resolver(resolvePromise, rejectPromise);
		}
		catch (e) {
			rejectPromise(e);
		}
	}
	function invokeCallback(subscriber) {
		var owner = subscriber.owner;
		var settled = owner.state_;
		var value = owner.data_;
		var callback = subscriber[settled];
		var promise = subscriber.then;
		if (typeof callback === 'function') {
			settled = FULFILLED;
			try {
				value = callback(value);
			}
			catch (e) {
				reject(promise, e);
			}
		}
		if (!handleThenable(promise, value)) {
			if (settled === FULFILLED)
				resolve(promise, value);
			if (settled === REJECTED)
				reject(promise, value);
		}
	}
	function handleThenable(promise, value) {
		var resolved;
		try {
			if (promise === value)
				throw new TypeError('A promises callback cannot return that same promise.');
			if (value && (typeof value === 'function' || typeof value === 'object')) {
				var then = value.then;
				if (typeof then === 'function') {
					then.call(value, function (val) {
						if (!resolved) {
							resolved = true;
							if (value !== val)
								resolve(promise, val);
							else
								fulfill(promise, val);
						}
					}, function (reason) {
						if (!resolved) {
							resolved = true;
							reject(promise, reason);
						}
					});
					return true;
				}
			}
		}
		catch (e) {
			if (!resolved)
				reject(promise, e);
			return true;
		}
		return false;
	}
	function resolve(promise, value) {
		if (promise === value || !handleThenable(promise, value))
			fulfill(promise, value);
	}
	function fulfill(promise, value) {
		if (promise.state_ === PENDING) {
			promise.state_ = SEALED;
			promise.data_ = value;
			asyncCall(publishFulfillment, promise);
		}
	}
	function reject(promise, reason) {
		if (promise.state_ === PENDING) {
			promise.state_ = SEALED;
			promise.data_ = reason;
			asyncCall(publishRejection, promise);
		}
	}
	function publish(promise) {
		var callbacks = promise.then_;
		promise.then_ = undefined;
		for (var i = 0; i < callbacks.length; i++) {
			invokeCallback(callbacks[i]);
		}
	}
	function publishFulfillment(promise) {
		promise.state_ = FULFILLED;
		publish(promise);
	}
	function publishRejection(promise) {
		promise.state_ = REJECTED;
		publish(promise);
	}
	var Promise = function (resolver) {
		if (typeof resolver !== 'function')
			throw new TypeError('Promise constructor takes a function argument');
		if (this instanceof Promise === false)
			throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.');
		this.then_ = [];
		invokeResolver(resolver, this);
	};
	Promise.prototype = {
		constructor: Promise,
		state_: PENDING,
		then_: null,
		data_: undefined,
		then: function (onFulfillment, onRejection) {
			var subscriber = {
				owner: this,
				then: new this.constructor(NOOP),
				fulfilled: onFulfillment,
				rejected: onRejection
			};
			if (this.state_ === FULFILLED || this.state_ === REJECTED) {
				asyncCall(invokeCallback, subscriber);
			}
			else {
				this.then_.push(subscriber);
			}
			return subscriber.then;
		},
		'catch': function (onRejection) {
			return this.then(null, onRejection);
		}
	};
	Promise.all = function (promises) {
		var Class = this;
		if (!isArray(promises))
			throw new TypeError('You must pass an array to Promise.all().');
		return new Class(function (resolve, reject) {
			var results = [];
			var remaining = 0;
			function resolver(index) {
				remaining++;
				return function (value) {
					results[index] = value;
					if (!--remaining)
						resolve(results);
				};
			}
			for (var i = 0, promise; i < promises.length; i++) {
				promise = promises[i];
				if (promise && typeof promise.then === 'function')
					promise.then(resolver(i), reject);
				else
					results[i] = promise;
			}
			if (!remaining)
				resolve(results);
		});
	};
	Promise.race = function (promises) {
		var Class = this;
		if (!isArray(promises))
			throw new TypeError('You must pass an array to Promise.race().');
		return new Class(function (resolve, reject) {
			for (var i = 0, promise; i < promises.length; i++) {
				promise = promises[i];
				if (promise && typeof promise.then === 'function')
					promise.then(resolve, reject);
				else
					resolve(promise);
			}
		});
	};
	Promise.resolve = function (value) {
		var Class = this;
		if (value && typeof value === 'object' && value.constructor === Class)
			return value;
		return new Class(function (resolve) {
			resolve(value);
		});
	};
	Promise.reject = function (reason) {
		var Class = this;
		return new Class(function (resolve, reject) {
			reject(reason);
		});
	};
	global['Promise'] = Promise;
})(OfficeExtension);
